#ifndef __TIMEPOINT_H__
#define __TIMEPOINT_H__

#include <chrono>
#include <string>
#include <string.h>

static_assert(__cplusplus >= 201103L, "Only available in c++11");

using _Tp = std::chrono::system_clock::time_point;
using _St = std::chrono::system_clock;
using _Sd = std::chrono::system_clock::duration;
using SysTime = _St;

template <class P, class R = void>
using TimeInterval = std::chrono::duration<P, R>;

template <class P, class R = void>
using _Dur = std::chrono::duration<P, R>;

template <class P, class R = void>
inline std::chrono::system_clock::duration toSysDur(_Dur<P, R> dur)
{
    return std::chrono::duration_cast<_Sd>(dur);
}

inline std::chrono::seconds toSecDur(_Sd dur)
{
    return std::chrono::duration_cast<std::chrono::seconds>(dur);
}

inline std::chrono::milliseconds toMilliDur(_Sd dur)
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(dur);
}

inline std::chrono::microseconds toMicroDur(_Sd dur)
{
    return std::chrono::duration_cast<std::chrono::microseconds>(dur);
}

constexpr std::chrono::seconds operator""_s(unsigned long long s)
{
    return std::chrono::seconds(s);
}

constexpr std::chrono::duration<long double> operator""_s(long double s)
{
    return std::chrono::duration<long double>(s);
}

constexpr std::chrono::milliseconds operator""_ms(unsigned long long s)
{
    return std::chrono::milliseconds(s);
}

constexpr std::chrono::microseconds operator""_us(unsigned long long s)
{
    return std::chrono::microseconds(s);
}

constexpr std::chrono::nanoseconds operator""_ns(unsigned long long s)
{
    return std::chrono::nanoseconds(s);
}

class TimePoint
{
  public:
    TimePoint() : tp_(_St::now())
    {
    }

    explicit TimePoint(_Tp tp) : tp_(std::move(tp))
    {
    }

    explicit TimePoint(time_t t)
        : tp_(_Tp{} + toSysDur(std::chrono::seconds(t)))
    {
    }

    std::chrono::seconds toSeconds()
    {
        return toSecDur(tp_.time_since_epoch());
    }

    std::chrono::microseconds toMicroseconds()
    {
        return toMicroDur(tp_.time_since_epoch());
    }

    std::chrono::milliseconds toMilliseconds()
    {
        return toMilliDur(tp_.time_since_epoch());
    }

    const time_t to_time_t() const
    {
        return _St::to_time_t(tp_);
    }

    std::string toString() const
    {
        char buf[32]{};
        tm tm_;
        time_t t_ = to_time_t();
        localtime_r(&t_, &tm_);
        strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", &tm_);
        return buf;
    }

    std::string toCustomString(const char* fmtstr)
    {
        char buf[64] {};
        tm tm_;
        time_t t_ = to_time_t();
        localtime_r(&t_, &tm_);
        strftime(buf, sizeof(buf) - 1, fmtstr, &tm_);
        return buf;
    }

    static size_t formatStr(char* dest, size_t len, const time_t& t)
    {
        char buf[32]{};
        tm tm_;
        localtime_r(&t, &tm_);
        size_t n = strftime(buf, sizeof(buf) - 1, "%Y-%m-%d %H:%M:%S", &tm_);
        strncpy(dest, buf, std::min(len, n));
        return n;
    }

    static const TimePoint Now()
    {
        return TimePoint();
    }

    static const timespec diffAsTimeSpec(const TimePoint& now,
                                         const TimePoint& then)
    {
        auto nsec_dur = then - now;
        auto sec_dur = toSecDur(nsec_dur);
        return {sec_dur.count(), (nsec_dur - sec_dur).count()};
    }

  public:
    TimePoint(TimePoint&&) = default;
    TimePoint(const TimePoint&) = default;
    TimePoint& operator=(const TimePoint&) = default;
    TimePoint& operator=(TimePoint&&) = default;

    template <class P, class R = void>
    TimePoint operator+(_Dur<P, R> dur) const
    {
        auto d = toSysDur<P, R>(dur);
        return TimePoint(tp_ + d);
    }

    void swap(TimePoint& that) noexcept
    {
        std::swap(tp_, that.tp_);
    }

    _Sd operator-(const TimePoint& that) const
    {
        return tp_ - that.tp_;
    }

    bool operator<(const TimePoint& that) const
    {
        return tp_ < that.tp_;
    }

    bool operator==(const TimePoint& that) const
    {
        return tp_ == that.tp_;
    }

    bool operator>(const TimePoint& that) const
    {
        return tp_ > that.tp_;
    }

  private:
    _Tp tp_;
};

static_assert(sizeof(TimePoint) == sizeof(int64_t),
              "TimePoint should be sizeof int64_t");

#endif  // __TIMEPOINT_H__